import { createConnection, Socket } from 'net';
import { ResponseParser } from './parser';

interface RequestOptions {
  /**
   * 请求方法
   */
  method?: string;

  /**
   * 主机 IP
   */
  host: string;

  /**
   * 端口
   */
  port?: number;

  /**
   * 请求路径
   */
  path?: string;

  /**
   * 请求主体
   */
  body?: Record<string, string>;

  /**
   * 请求头部
   */
  headers?: Record<string, unknown>;
}

export class Request {
  private method: string;

  private host: string;
  private port: number;

  private path: string;

  private body: Record<string, string>;
  private headers: Record<string, unknown>;

  private bodyText: string = '';

  constructor(options: RequestOptions) {
    this.method = options.method ?? 'GET';

    this.host = options.host;
    this.port = options.port ?? 80;

    this.path = options.path ?? '/';

    this.body = options.body ?? Object.create(null);
    this.headers = options.headers ?? Object.create(null);

    if (this.headers['Content-Type'] == null) {
      this.headers['Content-Type'] = 'application/x-www-form-urlencoded';
    }

    switch (this.headers['Content-Type']) {
      case 'application/json': {
        this.bodyText = JSON.stringify(this.body);
        break;
      }

      case 'application/x-www-form-urlencoded': {
        this.bodyText = Object.keys(this.body)
          .map(key => {
            return `${key}=${encodeURIComponent(this.body[key])}`;
          })
          .join('&');
        break;
      }
    }

    this.headers['Content-Length'] = this.bodyText.length;
  }

  /**
   * 发送 HTTP 请求
   */
  send(connection?: Socket) {
    return new Promise((resolve, reject) => {
      const parser = new ResponseParser();

      if (connection) {
        connection.write(this.toString());
      } else {
        connection = createConnection({
          host: this.host,
          port: this.port,
        }, () => {
          connection!.write(this.toString());
        });
      }
  
      connection.on('data', data => {
        parser.receive(data.toString());
  
        if (parser.isFinished) {
          resolve(parser.response);
          connection!.end();
        }
      });
  
      connection.on('error', error => {
        reject(error);
        connection!.end();
      });
    });
  }

  toString() {
    const CRLF = '\r\n';

    const request = [
      `${this.method} ${this.path} HTTP/1.1`,
      CRLF,
      `${Object.keys(this.headers).map(key => `${key}: ${this.headers[key]}`).join('\r\n')}`,
      CRLF,
      CRLF,
      `${this.bodyText}`
    ];

    return request.join('');
  }
}
